using System;
using Framework;
using UnityEngine;
using System.Collections.Generic;

namespace GameCore
{
    public class PlayerInputHandler : MonoSingleton<PlayerInputHandler>
    {
        #region Watch Keys
        // Any key down
        private readonly HashSet<Action> anyKeyDown = new();
        public static void AddKeyDownListener(Action listener) => Instance.anyKeyDown.Add(listener);
        public static void RemoveKeyDownListener(Action listener) => Instance.anyKeyDown.Remove(listener);
        // Any key pressed
        private readonly HashSet<Action> anyKeyPressed = new();
        public static void AddKeyPressedListener(Action listener) => Instance.anyKeyPressed.Add(listener);
        public static void RemoveKeyPressedListener(Action listener) => Instance.anyKeyPressed.Remove(listener);
        // Key down
        private readonly Dictionary<KeyCode, HashSet<Action>> keyDown = new();

        public static void AddKeyDownListener(KeyCode keyCode, Action listener)
        {
            if (!Instance.keyDown.ContainsKey(keyCode)) Instance.keyDown.Add(keyCode, new HashSet<Action>());
            Instance.keyDown[keyCode].Add(listener);
        }

        public static void RemoveKeyDownListener(KeyCode keyCode, Action listener)
        {
            if (!Instance.keyDown.TryGetValue(keyCode, out HashSet<Action> actions)) return;
            actions.Remove(listener);
        }
        // Key up
        private readonly Dictionary<KeyCode, HashSet<Action>> keyUp = new();

        public static void AddKeyUpListener(KeyCode keyCode, Action listener)
        {
            if (!Instance.keyUp.ContainsKey(keyCode)) Instance.keyUp.Add(keyCode, new HashSet<Action>());
            Instance.keyUp[keyCode].Add(listener);
        }

        public static void RemoveKeyUpListener(KeyCode keyCode, Action listener)
        {
            if (!Instance.keyDown.TryGetValue(keyCode, out HashSet<Action> actions)) return;
            actions.Remove(listener);
        }
        
        // Key pressed
        private readonly Dictionary<KeyCode, HashSet<Action>> keyPressed = new();

        public static void AddKeyPressedListener(KeyCode keyCode, Action listener)
        {
            if (!Instance.keyPressed.ContainsKey(keyCode)) Instance.keyPressed.Add(keyCode, new HashSet<Action>());
            Instance.keyPressed[keyCode].Add(listener);
        }

        public static void RemoveKeyPressedListener(KeyCode keyCode, Action listener)
        {
            if (!Instance.keyPressed.TryGetValue(keyCode, out HashSet<Action> actions)) return;
            actions.Remove(listener);
        }

        private void CallListeners(HashSet<Action> listeners)
        {
            foreach (var listener in listeners)
                listener.Invoke();
        }
        
        private void WatchKeys()
        {
            if (Input.anyKeyDown) CallListeners(anyKeyDown);
            if (Input.anyKey) CallListeners(anyKeyDown);
            foreach (KeyCode k in System.Enum.GetValues(typeof(KeyCode)))
            {
                if (Input.GetKeyDown(k) && keyDown.TryGetValue(k, out var downListeners))
                    CallListeners(downListeners);
                if (Input.GetKeyUp(k) && keyUp.TryGetValue(k, out var upListeners))
                    CallListeners(upListeners);
                if (Input.GetKey(k) && keyPressed.TryGetValue(k, out var pressedListeners))
                    CallListeners(pressedListeners);
            }
        }
        
        #endregion
        
        private Camera mainCam;

        private readonly HashSet<object> blocks = new();
        public static bool IsBlocked => Instance.blocks.Count > 0;
        public static void Block(object obj) => Instance.blocks.Add(obj);
        public static void Unblock(object obj) => Instance.blocks.Remove(obj);
        
        public static Vector3 GetMouseWorldPosition() => Instance.mainCam.ScreenToWorldPoint(Input.mousePosition);
        
        protected override void Awake()
        {
            base.Awake();
            mainCam = Camera.main;
            SetState(null);
        }
        
        public void Update()
        {
            WatchKeys();
            if (!IsBlocked) currState.Update();
        }
        
        #region State Management
        
        public PlayerInputState currState;

        private void SetState(PlayerInputState newState = null, bool apply = false)
        {
            if (apply) currState?.Apply();
            else currState?.Cancel();
            currState?.Exit();
            currState = newState ?? new IdleState();
            currState?.Init(this);
        }
        
        public static PlayerInputState GetCurrentState() => Instance.currState;
        #endregion

        #region Cell Hovering
        
        private Cell _hoveringCell;
        
        public static void SetHoveringCell(Cell cell)
        {
            if (IsBlocked) return;
            if (cell == Instance._hoveringCell) return;
            Instance.currState.OnHoveringCellChanged(Instance._hoveringCell, cell);
            Instance._hoveringCell = cell;
        }
        
        public static void ClearHoveringCell(Cell simpleCell)
        {
            if (IsBlocked) return;
            if (simpleCell == Instance._hoveringCell)
                ClearHoveringCell();
        }

        public static void ClearHoveringCell()
        {
            if (IsBlocked) return;
            Instance.currState.OnHoveringCellChanged(Instance._hoveringCell, null);
            Instance._hoveringCell = null;
        }
        
        #endregion
        
        #region Drag Object

        public static void BeginDragObject(DraggableObject draggable)
        {
            if (Instance.currState is not IdleState) return;
            var state = new DragItemState(draggable);
            Instance.SetState(state);
        }

        public static void EndDragObject()
        {
            if (Instance.currState is DragItemState)
                Instance.SetState(null);
        }
        
        #endregion
        
        #region WorldItemPlacing

        public static void BeginPlaceWorldItem(WorldItem worldItem)
        {
            if (Instance.currState is not IdleState) return;
            Instance.SetState(new PlaceWorldItemState(worldItem));
        }

        public static void ApplyPlaceWorldItem()
        {
            if (Instance.currState is not PlaceWorldItemState state) return;
            Instance.SetState(null, true);
        }

        public static void CancelPlaceWorldItem()
        {
            if (Instance.currState is not PlaceWorldItemState state) return;
            Instance.SetState(null, false);
        }
        
        #endregion
        
        #region Place Grid Item

        public static void BeginPlaceGridItem(GridItem gridItem)
        {
            if (Instance.currState is not IdleState) return;
            var state = new PlaceGridItemState(gridItem);
            
            Instance.SetState(state);
        }

        public static void ApplyPlaceGridItem()
        {
            if (Instance.currState is not PlaceGridItemState state) return;
            Instance.SetState(null, true);
        }

        public static void CancelPlaceGridItem()
        {
            if (Instance.currState is not PlaceGridItemState state) return;
            Instance.SetState(null, false);
        }

        #endregion


    }
}